#pragma once

class CMacro;
class CMacroParam;

#include "CScrLanguage.h"

class CTokens
{
	CLListEx<CPool<CToken>*>	m_TokenPools;
	int							m_nNumPools;
	int							m_nNumTokens;

public:
	CTokens()
	{
		m_TokenPools.Add(new CPool<CToken>(DEFAULT_NUM_TOKENS));
		m_nNumPools = 1;
		m_nNumTokens = 0;
	}
	~CTokens()
	{
		for(auto pLink = m_TokenPools.Begin(); pLink != m_TokenPools.Bad(); ++pLink)
		{
			delete *pLink;
		}
	}
	
	inline void		*	Add()
	{
		for(auto pLink = m_TokenPools.Begin(); pLink != m_TokenPools.Bad(); ++pLink)
		{
			if(pLink->HasFreeSlots())
			{
				++m_nNumTokens;
				return pLink->Allocate();
			}
		}
		if(auto pPool = new CPool<CToken>(DEFAULT_NUM_TOKENS))
		{
			void * pToken = pPool->Allocate();
			if(pToken)
			{
				m_TokenPools.Add(pPool);
				++m_nNumTokens;
			}
			return pToken;
		}
		return nullptr;
	}
	inline void				Delete(CToken *pToken)
	{
		if(!pToken) return;
		for(auto pLink = m_TokenPools.Begin(); pLink != m_TokenPools.Bad(); ++pLink)
		{
			if(pLink->Contains(pToken))
			{
				pLink->Deallocate(pToken);
				break;
			}
		}
	}
};
extern CTokens Tokens;

class CToken
{
	eTokenType				m_bType;
	PType					m_bValueType;
	bool					m_bSkipInCompile	: 1;
	bool					m_bNoTerminator		: 1;
	bool					m_bTranslated		: 1;
	bool					m_bSigned			: 1;
	short					m_nConstructID;
	short					m_nSizeOf;
	short					m_nTranslatedSize;
	const CTypeBase		*	m_pArgType;
	const CTranslation	*	m_pTranslation;
	char				*	m_pTranslatedBuffer;

	// The token is usually meant for holding ONE of these...
	union
	{
		const CEnumerator		*	m_pEnumerator;
		CVariable				*	m_pVariable;
		CLabel					*	m_pLabel;
		COperator				*	m_pOperator;
		const COperation		*	m_pOperation;
		const CCommand			*	m_pCommand;
		CIdentifier				*	m_pIdentifier;
		COpcode					*	m_pOpcode;
		CMacro					*	m_pMacro;
		CMacroParam				*	m_pMacroParam;
		int							m_iVal;
		DWORD						m_dwVal;
		float						m_fVal;
	};

	// This token might have a "sub-token" to support the union above - e.g. an index var for an array variable
	CToken				*	m_pSubToken;

	std::string		m_sString;

	// good riddance - std::string saves loads bytes per token and therefore MB's of RAM per build!
	//char			m_szString[MAX_STRING_LENGTH+2];

public:
	CToken(eTokenType type, const char * szString = nullptr)	:	m_bType(type),
																	m_bValueType(PT_NONE),
																	m_pArgType(nullptr),
																	m_bSkipInCompile(false),
																	m_bNoTerminator(false),
																	m_bTranslated(false),
																	m_bSigned(true),
																	m_nConstructID(0),
																	m_nSizeOf(-1),
																	m_nTranslatedSize(-1),
																	m_pTranslation(nullptr),
																	m_dwVal(NULL),
																	m_pSubToken(nullptr),
																	m_sString(szString ? szString : "")
	{
	};

	inline void SetString(const char *szStr)				{m_sString = szStr;}
	inline void SetType(eTokenType type)					{m_bType = type;}
	inline void SetValueType(const CTypeBase * pType)		{m_pArgType = pType; m_bValueType = pType->GetBasicType();}
	inline void SetValueType(PType pt)						{m_bValueType = pt;}
	inline void	SetValue(DWORD value)						{m_dwVal = value;}
	inline void	SetValue(int value)							{m_iVal = value;}
	inline void	SetValue(float value)						{m_fVal = value;}
	inline void SetValue(double value)						{m_fVal = (float)value;}
	inline void SetValue(CVariable *value)					{m_pVariable = value;}
	inline void SetValue(CLabel *value)						{m_pLabel = value;}
	inline void SetValue(COperator *value)					{m_pOperator = value;}
	inline void SetValue(const COperation *value)			{m_pOperation = value;}
	inline void SetValue(const CCommand *value)				{m_pCommand = value;}
	inline void SetValue(CMacro *value)						{m_pMacro = value;}
	inline void SetValue(CMacroParam *value)				{m_pMacroParam = value;}
	inline void SetValue(const CEnumerator *value)			{m_pEnumerator = value;}
	inline void SetSubToken(CToken *pToken)					{m_pSubToken = pToken;}
	inline void SetConstruct(short value)					{m_nConstructID = value;}
	inline void IsSkippedDuringCompilation(bool b)			{m_bSkipInCompile = b;}
	inline bool IsSkippedDuringCompilation() const			{return m_bSkipInCompile;}
	inline void SkipTerminator(bool b)						{m_bNoTerminator = b;}
	inline bool SkipTerminator() const						{return m_bNoTerminator;}
	inline bool IsTranslated() const						{return m_bTranslated;}
	inline eTokenType GetType() const						{return m_bType;}
	inline const CTypeBase * GetArgType() const				{return m_pArgType;}
	inline PType GetArgBasicType() const					{return m_pArgType ? m_pArgType->GetBasicType() : m_bValueType;}
	inline const char * GetString()	const					{return m_sString.c_str();}
	inline void GetString(char *szStr) const				{strncpy(szStr, m_sString.c_str(), m_sString.length());}
	inline PType GetValueType() const						{return m_bValueType ? (m_pArgType ? m_pArgType->GetBasicType() : m_bValueType) : m_bValueType;}
	inline const CTranslation * GetTranslation() const		{return m_bTranslated ? m_pTranslation : nullptr;}
	inline COperator * GetOperatorValue() const				{return m_pOperator;}
	inline const COperation * GetOperationValue() const		{return m_pOperation;}
	inline CVariable * GetVariableValue() const				{return m_pVariable;}
	inline const CCommand * GetCommandValue() const			{return m_pCommand;}
	inline COpcode * GetOpcodeValue() const					{return m_pOpcode;}
	inline CLabel * GetLabelValue() const					{return m_pLabel;}
	inline CMacro * GetMacroValue()	const					{return m_pMacro;}
	inline CMacroParam * GetMacroParamValue() const			{return m_pMacroParam;}
	inline float GetFloatValue() const						{return m_fVal;}
	inline int GetIntValue() const							{return m_iVal;}
	inline CToken * GetSubToken() const						{return m_pSubToken;}
	inline short GetConstruct() const						{return m_nConstructID;}
	inline int GetStringLength() const						{return m_sString.length();}
	inline short GetTranslatedSize() const					{return m_nTranslatedSize;}

	short Translate(const CTranslation * pTranslation)
	{
		if(!pTranslation) return -1;
		m_pTranslation = pTranslation;
		m_nTranslatedSize = 0;

		for(int i=0, l=pTranslation->GetNumElements(); i < l; ++i)
		{
			int val;
			int idx;

			// Add the number of bytes each element needs to the overall size
			switch(pTranslation->GetElement(i))
			{
			case TRANSLATE_ELEM_VAL:
				// This is just a written translation value (e.g. for a data type), just add the number of bytes it needs (unsigned)
				m_nTranslatedSize += GetDWordNumBytes(pTranslation->GetElementValue(i));
				continue;
			case TRANSLATE_ELEM_OPERAND:
				// This is something stored in a token related to this one, so the size purely depends on their values
				switch(idx = pTranslation->GetElementValue(i))
				{
				case TRANSLATE_OPERAND_VAR:
				case TRANSLATE_OPERAND_IDXVAR:
					--idx;
				case TRANSLATE_OPERAND_VAL:
					if(pTranslation->GetCondition(idx) == TRANSLATE_COND_STRLEN_STR)
					{
						if(pTranslation->IsConditionSizeConst(idx))
							m_nTranslatedSize += pTranslation->GetConditionValue(idx);
						else
							m_nTranslatedSize += GetStringLength() + 1;
					}
					else
					{
						switch(val = pTranslation->GetConditionValue(idx))
						{
						case DT_INT32:
						case DT_FLOAT32:
							m_nTranslatedSize += 4;
							break;
						case DT_INT24:
						case DT_FLOAT24:
							m_nTranslatedSize += 3;
							break;
						case DT_INT16:
						case DT_FLOAT16:
						case DT_FIXED16:
							m_nTranslatedSize += 2;
							break;
						case DT_INT8:
						case DT_FLOAT8:
							m_nTranslatedSize += 1;
							break;
						default:
							m_nTranslatedSize += val;
							break;
						case DT_NULL: break;
						}
					}
					break;
				}
				continue;
			case TRANSLATE_ELEM_COND:
				if(pTranslation->IsConditionSizeConst(pTranslation->GetElementValue(i)))
					m_nTranslatedSize += GetDWordNumBytes(pTranslation->GetConditionValue(pTranslation->GetElementValue(i)));
				else
				{
					m_nTranslatedSize += GetDWordNumBytes(GetValueSize());
				}
				continue;
			}
			break;
		}

		m_bTranslated = true;
		return m_nTranslatedSize;
	}

	short GetSize() const
	{
		if(m_bTranslated) return m_nTranslatedSize;
		if(m_bSkipInCompile) return 0;
		switch(GetType())
		{
		case TOKEN_HEX_BYTE: return sizeof(BYTE);
		case TOKEN_HEX_WORD: return sizeof(WORD);
		case TOKEN_HEX_DWORD: return sizeof(DWORD);
		case TOKEN_HEXMAYBELABEL: return sizeof(DWORD);
		case TOKEN_OPERATOR:
		case TOKEN_COMMAND: return GetDWordNumBytes(MAX_OPCODES);
		case TOKEN_VAR:
		case TOKEN_VARINDEX: return GetDWordNumBytes(CLimits::GetMaxVar());
		case TOKEN_STRING:
			return GetStringLength();
		case TOKEN_LABEL:
			if(GetLabelValue()->IsHex())
				return sizeof(DWORD);
		case TOKEN_VALUE:
		default:
			break;
		}
		return GetValueSize();
	}

	short GetValueSize() const
	{
		if(m_nSizeOf >= 0) return m_nSizeOf;
		if(m_bSkipInCompile) return 0;
		
		switch(GetType())
		{
		case TOKEN_HEX_BYTE:
			return sizeof(BYTE);
		case TOKEN_HEX_WORD:
			return sizeof(WORD);
		case TOKEN_HEX_DWORD:
			return sizeof(DWORD);
		case TOKEN_HEXMAYBELABEL:
			return sizeof(DWORD);
		case TOKEN_COMMAND:
			return m_bSigned ? GetIntNumBytes(GetCommandValue()->GetId()) : GetDWordNumBytes(GetCommandValue()->GetId());
		case TOKEN_OPERATOR:
			return m_bSigned ? GetIntNumBytes(GetOperationValue()->GetId()) : GetDWordNumBytes(GetOperationValue()->GetId());
		case TOKEN_VAR:
			return m_bSigned ? GetIntNumBytes(GetVariableValue()->GetIndex()) : GetDWordNumBytes(GetVariableValue()->GetIndex());
		case TOKEN_VARINDEX:
			return m_bSigned ? GetIntNumBytes(GetIntValue()) : GetDWordNumBytes(GetIntValue());
		case TOKEN_LABEL:
			return m_bSigned ? GetIntNumBytes(m_pLabel->GetOffset()) : GetDWordNumBytes(m_pLabel->GetOffset());
		case TOKEN_VALUE:
			if(GetValueType() & PT_FLOAT) return sizeof(float);
			else if(GetValueType() & PT_INT) return m_bSigned ? GetIntNumBytes(GetIntValue()) : GetDWordNumBytes(GetIntValue());
			break;
		case TOKEN_STRING:
			return GetStringLength() + 1;
		case TOKEN_CONSTRUCT:
			return 0;
		default:
			throw("***FIX GetValueSize()***");
		}
		return 0;
	}
	short GetValueSize()
	{
		// change this to use translations instead of predefined characteristics
		if(m_nSizeOf >= 0) return m_nSizeOf;
		if(m_bSkipInCompile) return m_nSizeOf = 0;

		switch(GetType())
		{
		case TOKEN_HEX_BYTE:
			return m_nSizeOf = sizeof(BYTE);
		case TOKEN_HEX_WORD:
			return m_nSizeOf = sizeof(WORD);
		case TOKEN_HEX_DWORD:
			return m_nSizeOf = sizeof(DWORD);
		case TOKEN_HEXMAYBELABEL:
			return m_nSizeOf = sizeof(DWORD);
		case TOKEN_COMMAND:
			return m_nSizeOf = m_bSigned ? GetIntNumBytes(GetCommandValue()->GetId()) : GetDWordNumBytes(GetCommandValue()->GetId());
		case TOKEN_OPERATOR:
			return m_nSizeOf = m_bSigned ? GetIntNumBytes(GetOperationValue()->GetId()) : GetDWordNumBytes(GetOperationValue()->GetId());
		case TOKEN_VAR:
			return m_nSizeOf = m_bSigned ? GetIntNumBytes(GetVariableValue()->GetIndex()) : GetDWordNumBytes(GetVariableValue()->GetIndex());
		case TOKEN_VARINDEX:
			return m_nSizeOf = m_bSigned ? GetIntNumBytes(GetIntValue()) : GetDWordNumBytes(GetIntValue());
		case TOKEN_LABEL:
			return m_bSigned ? GetIntNumBytes(m_pLabel->GetOffset()) : GetDWordNumBytes(m_pLabel->GetOffset());
		case TOKEN_VALUE:
			if(GetValueType() & PT_FLOAT) m_nSizeOf = sizeof(float);
			else if(GetValueType() & PT_INT) m_nSizeOf = m_bSigned ? GetIntNumBytes(GetIntValue()) : GetDWordNumBytes(GetIntValue());
			return m_nSizeOf;
		case TOKEN_STRING:
			return GetStringLength() + 1;
		case TOKEN_CONSTRUCT:
			return m_nSizeOf = 0;
		default:
			throw("***FIX GetValueSize()***");
		}
		return m_nSizeOf;
	}

	void		*	operator new(size_t s)				{
		return Tokens.Add();
	}
	void			operator delete(void * p)			{
		return Tokens.Delete((CToken*)p);
	}
};